//
// Created by Göksu Güvendiren on 2019-05-14.
//

#include "Scene.hpp"


void Scene::buildBVH() {
    printf(" - Generating BVH...\n\n");
    this->bvh = new BVHAccel(objects, 1, BVHAccel::SplitMethod::NAIVE);
}

Intersection Scene::intersect(const Ray &ray) const
{
    return this->bvh->Intersect(ray);
}

void Scene::sampleLight(Intersection &pos, float &pdf) const
{
    float emit_area_sum = 0;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        if (objects[k]->hasEmit()){
            emit_area_sum += objects[k]->getArea();
        }
    }
    float p = get_random_float() * emit_area_sum;
    emit_area_sum = 0;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        if (objects[k]->hasEmit()){
            emit_area_sum += objects[k]->getArea();
            if (p <= emit_area_sum){
                objects[k]->Sample(pos, pdf);
                break;
            }
        }
    }
}

bool Scene::trace(
        const Ray &ray,
        const std::vector<Object*> &objects,
        float &tNear, uint32_t &index, Object **hitObject) const
{
    *hitObject = nullptr;
    for (uint32_t k = 0; k < objects.size(); ++k) {
        float tNearK = kInfinity;
        uint32_t indexK;
        Vector2f uvK;
        if (objects[k]->intersect(ray, tNearK, indexK) && tNearK < tNear) {
            *hitObject = objects[k];
            tNear = tNearK;
            index = indexK;
        }
    }


    return (*hitObject != nullptr);
}

constexpr int kMaxDepth = 5;

Vector3f Scene::castRay(const Ray &ray, int depth) const
{
    if (depth > kMaxDepth) return Vector3f{};
    // TODO Implement Path Tracing Algorithm here
    Vector3f L_dir, L_indir;
    Intersection itsc = Scene::intersect(ray); 
    if (!itsc.happened) 
        return Vector3f{};
    Material* m = itsc.m;
    //交点是光源
    if (m->hasEmission())
        return m->getEmission();
    
    Vector3f wi = -ray.direction;
    
    Intersection lightItsc; float lightPdf;
    //对光源采样，获取采样位置和概率密度
    sampleLight(lightItsc, lightPdf); 
    Vector3f wsDis = lightItsc.coords - itsc.coords;
    float dis = dotProduct(wsDis, wsDis);
    Vector3f ws = normalize(wsDis);
    Intersection testItsc = Scene::intersect(Ray(itsc.coords, ws));
    if (testItsc.happened && testItsc.m->hasEmission()){
        return L_dir = lightItsc.emit * m->eval(wi, ws, itsc.normal) * dotProduct(ws, itsc.normal) * dotProduct(-ws, lightItsc.normal) / dis / lightPdf;
    }

    //俄罗斯轮盘赌确定是否继续path tracing
    if (get_random_float() < RussianRoulette) {
        constexpr int kMaxSample = 8;
        for (int ii = 0; ii < kMaxSample; ++ii) {
            Vector3f wo = m->sample(wi, itsc.normal).normalized();
            Ray objRay(itsc.coords, wo);
//        Intersection testItsc1 = Scene::intersect(objRay);
//        if (testItsc1.happened && !testItsc1.m->hasEmission()) {
            L_indir += castRay(objRay, depth + 1) * m->eval(wi, wo, itsc.normal) * dotProduct(wo, itsc.normal) / m->pdf(wi, wo, itsc.normal) / RussianRoulette / kMaxSample;
//        }
        }
    }
    return L_dir + L_indir;
}